


#include "qe_log.h"
#include "qe_shell.h"
#include "qe_string.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_version.h"



QELOG_DOMAIN(QELOG_DOMAIN_SHELL);



enum
{
#if defined(CONFIG_SHELL_SHOW_INFO)
    SHELL_TXT_INFO,
#endif
    SHELL_TXT_CMD_TOO_LONG,
    SHELL_TXT_CMD_LIST,
    SHELL_TXT_VAR_LIST,
    SHELL_TXT_USER_LIST,
    SHELL_TXT_KEY_LIST,
    SHELL_TXT_CMD_NOT_FOUND,
    SHELL_TXT_POINT_CANNOT_MODIFY,
    SHELL_TXT_VAR_READ_ONLY_CANNOT_MODIFY,
    SHELL_TXT_NOT_VAR,
    SHELL_TXT_VAR_NOT_FOUND,
    SHELL_TXT_HELP_HEADER,
    SHELL_TXT_PASSWORD_HINT,
    SHELL_TXT_PASSWORD_ERROR,
    SHELL_TXT_CLEAR_CONSOLE,
    SHELL_TXT_CLEAR_LINE,
    SHELL_TXT_TYPE_CMD,
    SHELL_TXT_TYPE_VAR,
    SHELL_TXT_TYPE_USER,
    SHELL_TXT_TYPE_KEY,
    SHELL_TXT_TYPE_NONE,
#if QE_SHELL_EXEC_UNDEF_FUNC == 1
    qe_shellEXT_PARAM_ERROR,
#endif
};



#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_START(start);
QE_SHELL_EXPORT_END(end);
QE_SHELL_EXPORT_USR(QE_SHELL_DEFAULT_USER, QE_SHELL_DEFAULT_PSWD, default user);
    #if defined(__CC_ARM) || (defined(__ARMCC_VERSION) && __ARMCC_VERSION >= 6000000)
        extern const unsigned int qe_shell_command$$base;
        extern const unsigned int qe_shell_command$$limit;
    #elif defined(__ICCARM__) || defined(__ICCRX__)
        #pragma section=".qe_shellcommand"
    // #elif defined(__GNUC__)
    //const unsigned int qe_shellcommand_start;
    //const unsigned int qe_shellcommand_end;
    #endif
#endif

static qe_shell *shctx = QE_NULL;

static const char *qe_shellext[] =
{
// #if defined(CONFIG_SHELL_SHOW_INFO)
//     [SHELL_TXT_INFO] =
//         "\r\n"
//         "   ___  ___ ___ ___   ___ _  _ ___ _    _    \r\n"
//         "  / _ \\| __/ __| __| / __| || | __| |  | |   \r\n"
//         " | (_) | _|\\__ \\ _|  \\__ \\ __ | _|| |__| |__ \r\n"
//         "  \\__\\_\\___|___/_|   |___/_||_|___|____|____|\r\n"
//         "\r\n"
//         "Build:       "__DATE__" "__TIME__"\r\n"
//         "Version:     "QELIB_VERSION"\r\n"
//         "Copyright:   (c) 2023 QELIB\r\n",
// #endif
#if defined(CONFIG_SHELL_SHOW_INFO)
    [SHELL_TXT_INFO] =
        "\r\n"
        "______  ___  ______________________     _____________  ______________________ \r\n"
        "___  / / / \\/ /__  /____  _/__  __ )    __  ___/__  / / /__  ____/__  /___  / \r\n"
        "__  /_/ /__  /__  /  __  / __  __  |    _____ \\__  /_/ /__  __/  __  / __  /  \r\n"
        "_  __  / _  / _  /____/ /  _  /_/ /     ____/ /_  __  / _  /___  _  /___  /___\r\n"
        "/_/ /_/  /_/  /_____/___/  /_____/      /____/ /_/ /_/  /_____/  /____/_____/_ \r\n"                                      
        "\r\n"
        "Build:       "__DATE__" "__TIME__"\r\n"
        "Version:     "QELIB_VERSION"\r\n"
        "Copyright:   (c) 2023 QELIB\r\n",
#endif
    [SHELL_TXT_CMD_TOO_LONG] = 
        "\r\nWarning: Command is too long\r\n",
    [SHELL_TXT_CMD_LIST] = 
        "\r\nCommand List:\r\n",
    [SHELL_TXT_VAR_LIST] = 
        "\r\nVar List:\r\n",
    [SHELL_TXT_USER_LIST] = 
        "\r\nUser List:\r\n",
    [SHELL_TXT_KEY_LIST] =
        "\r\nKey List:\r\n",
    [SHELL_TXT_CMD_NOT_FOUND] = 
        "Command not Found\r\n",
    [SHELL_TXT_POINT_CANNOT_MODIFY] = 
        "can't set pointer\r\n",
    [SHELL_TXT_VAR_READ_ONLY_CANNOT_MODIFY] = 
        "can't set read only var\r\n",
    [SHELL_TXT_NOT_VAR] =
        " is not a var\r\n",
    [SHELL_TXT_VAR_NOT_FOUND] = 
        "Var not Fount\r\n",
    [SHELL_TXT_HELP_HEADER] =
        "command help of ",
    [SHELL_TXT_PASSWORD_HINT] = 
        "password:",
    [SHELL_TXT_PASSWORD_ERROR] = 
        "\r\npassword error\r\n",
    [SHELL_TXT_CLEAR_CONSOLE] = 
        "\033[2J\033[1H",
    [SHELL_TXT_CLEAR_LINE] = 
        "\033[2K\r",
    [SHELL_TXT_TYPE_CMD] = 
        "CMD ",
    [SHELL_TXT_TYPE_VAR] = 
        "VAR ",
    [SHELL_TXT_TYPE_USER] = 
        "USER",
    [SHELL_TXT_TYPE_KEY] = 
        "KEY ",
    [SHELL_TXT_TYPE_NONE] = 
        "NONE",
#if QE_SHELL_EXEC_UNDEF_FUNC == 1
    [qe_shellEXT_PARAM_ERROR] = 
        "Parameter error\r\n",
#endif
};

static unsigned short shell_string_copy(char *dest, char* src)
{
    unsigned short count = 0;
    while (*(src + count))
    {
        *(dest + count) = *(src + count);
        count++;
    }
    *(dest + count) = 0;
    return count;
}

static unsigned short write_string(qe_shell *sh, const char *string)
{
    unsigned short count = 0;
    const char *p = string;
    while(*p++)
    {
        count ++;
    }
    return sh->write(sh, (char *)string, count);
}

static void write_byte(qe_shell *sh, char data)
{
    sh->write(sh, &data, 1);
}

void shell_delete_cli(qe_shell *shell, unsigned char length)
{
    while (length--)
    {
        write_string(shell, "\b \b");
    }
}

void shell_clear_cli(qe_shell *shell)
{
    int i;
    for (i = shell->parser.length - shell->parser.cursor; i > 0; i--)
    {
        write_byte(shell, ' ');
    }
    shell_delete_cli(shell, shell->parser.length);
}

static void shell_history(qe_shell *shell, signed char dir)
{
    if (dir > 0) {
        if (shell->history.offset-- <= 
            -((shell->history.number > shell->history.record) ?
                shell->history.number : shell->history.record)) {
            shell->history.offset = -((shell->history.number > shell->history.record)
                                    ? shell->history.number : shell->history.record);
        }
    } else if (dir < 0) {
        if (++shell->history.offset > 0) {
            shell->history.offset = 0;
            return;
        }
    } else {
        return;
    }
    shell_clear_cli(shell);
    if (shell->history.offset == 0)
    {
        shell->parser.cursor = shell->parser.length = 0;
    }
    else
    {
        if ((shell->parser.length = shell_string_copy(shell->parser.buf->p,
                shell->history.item[(shell->history.record + CONFIG_SHELL_HISTORY_MAX_NUM
                    + shell->history.offset) % CONFIG_SHELL_HISTORY_MAX_NUM])) == 0)
        {
            return;
        }
        shell->parser.cursor = shell->parser.length;
        write_string(shell, (const char *)shell->parser.buf->p);
    }
}

static void shell_history_add(qe_shell *shell)
{
    shell->history.offset = 0;
    if (shell->history.number > 0
        && qe_strcmp(shell->history.item[(shell->history.record == 0 ? 
                CONFIG_SHELL_HISTORY_MAX_NUM : shell->history.record) - 1],
                shell->parser.buf->p) == 0)
    {
        return;
    }
    if (shell_string_copy(shell->history.item[shell->history.record],
                        shell->parser.buf->p) != 0)
    {
        shell->history.record++;
    }
    if (++shell->history.number > CONFIG_SHELL_HISTORY_MAX_NUM)
    {
        shell->history.number = CONFIG_SHELL_HISTORY_MAX_NUM;
    }
    if (shell->history.record >= CONFIG_SHELL_HISTORY_MAX_NUM)
    {
        shell->history.record = 0;
    }
}

static void shell_parser_param(qe_shell *shell)
{
    int i;
    unsigned char quotes = 0;
    unsigned char record = 1;
    char *buffer = (char *)shell->parser.buf->p;

    for (i = 0; i < CONFIG_SHELL_PARAM_MAX_NUM; i++)
    {
        shell->parser.param[i] = QE_NULL;
    }

    shell->parser.nr_params = 0;
    for (i = 0; i < shell->parser.length; i++)
    {
        if (quotes != 0
            || (buffer[i] != ' '
                && buffer[i] != 0))
        {
            if (buffer[i] == '\"')
            {
                quotes = quotes ? 0 : 1;
            }
            if (record == 1)
            {
                if (shell->parser.nr_params < CONFIG_SHELL_PARAM_MAX_NUM)
                {
                    shell->parser.param[shell->parser.nr_params++] =
                        &(buffer[i]);
                }
                record = 0;
            }
            if (buffer[i] == '\\'
                && buffer[i + 1] != 0)
            {
                i++;
            }
        }
        else
        {
            buffer[i] = 0;
            record = 1;
        }
    }
}

signed char qe_shello_hex(unsigned int value, char *buffer)
{
    char byte;
    unsigned char i = 8;
    buffer[8] = 0;
    while (value)
    {
        byte = value & 0x0000000F;
        buffer[--i] = (byte > 9) ? (byte + 87) : (byte + 48);
        value >>= 4;
    }
    return 8 - i;
}

static const char* shell_get_command_desc(struct qe_shell_command *command)
{
    if (command->attr.attrs.type <= QE_SHELL_TYPE_CMD_FUNC)
    {
        return command->data.cmd.desc;
    }
    else if (command->attr.attrs.type <= QE_SHELL_TYPE_VAR_NODE)
    {
        return command->data.var.desc;
    }
    else
    {
        return command->data.key.desc;
    }
}

static unsigned short shell_write_command_desc(qe_shell *sh, const char *string)
{
    unsigned short count = 0;
    const char *p = string;
    
    while (*p && *p != '\r' && *p != '\n')
    {
        p++;
        count++;
    }
    
    if (count > 36)
    {
        sh->write(sh, (char *)string, 36);
        sh->write(sh, "...", 3);
    }
    else
    {
        sh->write(sh, (char *)string, count);
    }
    return count > 36 ? 36 : 39;
}

static const char* shell_get_command_name(struct qe_shell_command *command)
{
    int i;
    static char buffer[9];
    for (i = 0; i < 9; i++)
    {
        buffer[i] = '0';
    }
    if (command->attr.attrs.type <= QE_SHELL_TYPE_CMD_FUNC)
    {
        return command->data.cmd.name;
    }
    else if (command->attr.attrs.type <= QE_SHELL_TYPE_VAR_NODE)
    {
        return command->data.var.name;
    }
    else if (command->attr.attrs.type <= QE_SHELL_TYPE_USR)
    {
        return command->data.usr.name;
    }
    else
    {
        qe_shello_hex(command->data.key.value, buffer);
        return buffer;
    }
}

struct qe_shell_command* shell_seek_command(qe_shell *shell,
                               const char *cmd,
                               struct qe_shell_command *base,
                               unsigned short compareLength)
{
    int i;
    const char *name;
    int count = shell->cmdtab.count -
        (int)(((void *)base - shell->cmdtab.base) / sizeof(struct qe_shell_command));
    for (i = 0; i < count; i++)
    {
        if (base[i].attr.attrs.type == QE_SHELL_TYPE_KEY)
        {
            continue;
        }
        name = shell_get_command_name(&base[i]);
        if (!name)
            continue;
        if (!compareLength)
        {
            if (qe_strcmp(cmd, name) == 0)
            {
                return &base[i];
            }
        }
        else
        {
            if (qe_strncmp(cmd, name, compareLength) == 0)
            {
                return &base[i];
            }
        }
    }
    return QE_NULL;
}

static void shell_remove_param_quotes(qe_shell *shell)
{
    int i;
    unsigned short paramLength;
    for ( i = 0; i < shell->parser.nr_params; i++)
    {
        if (shell->parser.param[i][0] == '\"')
        {
            shell->parser.param[i][0] = 0;
            shell->parser.param[i] = &shell->parser.param[i][1];
        }
        paramLength = qe_strlen(shell->parser.param[i]);
        if (shell->parser.param[i][paramLength - 1] == '\"')
        {
            shell->parser.param[i][paramLength - 1] = 0;
        }
    }
}

signed char qe_shello_dec(int value, char *buffer)
{
    unsigned char i = 11;
    int v = value;
    if (value < 0)
    {
        v = -value;
    }
    buffer[11] = 0;
    while (v)
    {
        buffer[--i] = v % 10 + 48;
        v /= 10;
    }
    if (value < 0)
    {
        buffer[--i] = '-';
    }
    if (value == 0) {
        buffer[--i] = '0';
    }
    return 11 - i;
}

static void shell_write_return_value(qe_shell *sh, int value)
{
    int i;
    char buffer[12] = "00000000000";
    write_string(sh, "Return: ");
    write_string(sh, &buffer[11 - qe_shello_dec(value, buffer)]);
    write_string(sh, ", 0x");
    for (i = 0; i < 11; i++)
    {
        buffer[i] = '0';
    }
    qe_shello_hex(value, buffer);
    write_string(sh, buffer);
    write_string(sh, "\r\n");
#if QE_SHELL_KEEP_RETURN_VALUE == 1
    shell->info.retVal = value;
#endif
}

static char shell_ext_parse_char(char *string)
{
    char *p = string + 1;
    char value = 0;

    if (*p == '\\')
    {
        switch (*(p + 1))
        {
        case 'b':
            value = '\b';
            break;
        case 'r':
            value = '\r';
            break;
        case 'n':
            value = '\n';
            break;
        case 't':
            value = '\t';
            break;
        case '0':
            value = 0;
            break;
        default:
            value = *(p + 1);
            break;
        }
    }
    else
    {
        value = *p;
    }
    return value;
}

static qe_shell_numtype_e shell_ext_num_type(char *string)
{
    char *p = string;
    qe_shell_numtype_e type = QE_SHELL_NUM_DEC;

    if ((*p == '0') && ((*(p + 1) == 'x') || (*(p + 1) == 'X')))
    {
        type = QE_SHELL_NUM_HEX;
    }
    else if ((*p == '0') && ((*(p + 1) == 'b') || (*(p + 1) == 'B')))
    {
        type = QE_SHELL_NUM_BIN;
    }
    else if (*p == '0')
    {
        type = QE_SHELL_NUM_OCT;
    }
    
    while (*p++)
    {
        if (*p == '.' && *(p + 1) != 0)
        {
            type = QE_SHELL_NUM_FLOAT;
            break;
        }
    }

    return type;
}

static char shell_ext2num(char code)
{
    if ((code >= '0') && (code <= '9'))
    {
        return code -'0';
    }
    else if ((code >= 'a') && (code <= 'f'))
    {
        return code - 'a' + 10;
    }
    else if ((code >= 'A') && (code <= 'F'))
    {
        return code - 'A' + 10;
    }
    else
    {
        return 0;
    }
}

static unsigned int shell_ext_parse_number(char *string)
{
    qe_shell_numtype_e type = QE_SHELL_NUM_DEC;
    char radix = 10;
    char *p = string;
    char offset = 0;
    signed char sign = 1;
    unsigned int valueInt = 0;
    float valueFloat = 0.0;
    unsigned int devide = 0;

    if (*string == '-')
    {
        sign = -1;
    }

    type = shell_ext_num_type(string + ((sign == -1) ? 1 : 0));

    switch ((char)type)
    {
    case QE_SHELL_NUM_HEX:
        radix = 16;
        offset = 2;
        break;
    
    case QE_SHELL_NUM_OCT:
        radix = 8;
        offset = 1;
        break;

    case QE_SHELL_NUM_BIN:
        radix = 2;
        offset = 2;
        break;
    
    default:
        break;
    }

    p = string + offset + ((sign == -1) ? 1 : 0);

    while (*p)
    {
        if (*p == '.')
        {
            devide = 1;
            p++;
            continue;
        }
        valueInt = valueInt * radix + shell_ext2num(*p);
        devide *= 10;
        p++;
    }
    if (type == QE_SHELL_NUM_FLOAT && devide != 0)
    {
        valueFloat = (float)valueInt / devide * sign;
        return *(unsigned int *)(&valueFloat);
    }
    else
    {
        return valueInt * sign;
    }
}

qe_base shell_get_var_value(qe_shell *shell, struct qe_shell_command *cmd)
{
    qe_base value = 0;

    switch (cmd->attr.attrs.type) {
    
    case QE_SHELL_TYPE_VAR_INT:
        value = *((int *)(cmd->data.var.value));
        break;
    
    case QE_SHELL_TYPE_VAR_SHORT:
        value = *((short *)(cmd->data.var.value));
        break;
    
    case QE_SHELL_TYPE_VAR_CHAR:
        value = *((char *)(cmd->data.var.value));
        break;
    
    case QE_SHELL_TYPE_VAR_STRING:
    
    case QE_SHELL_TYPE_VAR_POINT:
        value = (qe_base)cmd->data.var.value;
        break;
    
    case QE_SHELL_TYPE_VAR_NODE:
        value = ((qe_shell_node_attr *)cmd->data.var.value)->get ?
                    ((qe_shell_node_attr *)cmd->data.var.value)
                        ->get(((qe_shell_node_attr *)cmd->data.var.value)->var) : 0;
        break;
    default:
        break;
    }
    return value;
}

static qe_base shell_ext_parse_var(qe_shell *sh, char *var)
{
    qe_shell_command *cmd;
    
    cmd = shell_seek_command(sh, var + 1, sh->cmdtab.base, 0);
    if (cmd) {
        return shell_get_var_value(sh, cmd);
    } else {
        return 0;
    }
}

static char* shell_ext_parse_string(char *string)
{
    char *p = string;
    int index = 0;

    if (*string == '\"')
    {
        p = ++string;
    }

    while (*p)
    {
        if (*p == '\\')
        {
            *(string + index) = shell_ext_parse_char(p - 1);
            p++;
        }
        else if (*p == '\"')
        {
            *(string + index) = 0;
        }
        else
        {
            *(string + index) = *p;
        }
        p++;
        index ++;
    }
    *(string + index) = 0;
    return string;
}

qe_base shell_ext_parse_para(qe_shell *shell, char *string)
{
    if (*string == '\'' && *(string + 1))
    {
        return (qe_base)shell_ext_parse_char(string);
    }
    else if (*string == '-' || (*string >= '0' && *string <= '9'))
    {
        return (qe_base)shell_ext_parse_number(string);
    }
    else if (*string == '$' && *(string + 1))
    {
        return shell_ext_parse_var(shell, string);
    }
    else if (*string)
    {
        return (qe_base)shell_ext_parse_string(string);
    }
    return 0;
}

int shell_ext_run(qe_shell *shell, struct qe_shell_command *command, int argc, char *argv[])
{
    int i;
    qe_base params[CONFIG_SHELL_PARAM_MAX_NUM] = {0};
    int paramNum = command->attr.attrs.num_params > (argc - 1) ? 
        command->attr.attrs.num_params : (argc - 1);
    for (i = 0; i < argc - 1; i++)
    {
        params[i] = shell_ext_parse_para(shell, argv[i + 1]);
    }
    switch (paramNum)
    {
#if CONFIG_SHELL_PARAM_MAX_NUM >= 1
    case 0:
        return command->data.cmd.function();
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 1 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 2
    case 1:
        return command->data.cmd.function(params[0]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 2 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 3
    case 2:
        return command->data.cmd.function(params[0], params[1]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 3 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 4
    case 3:
        return command->data.cmd.function(params[0], params[1],
                                          params[2]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 4 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 5
    case 4:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 5 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 6
    case 5:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 6 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 7
    case 6:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 7 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 8
    case 7:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 8 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 9
    case 8:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6], params[7]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 9 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 10
    case 9:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6], params[7],
                                          params[8]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 10 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 11
    case 10:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6], params[7],
                                          params[8], params[9]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 11 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 12
    case 11:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6], params[7],
                                          params[8], params[9],
                                          params[10]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 12 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 13
    case 12:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6], params[7],
                                          params[8], params[9],
                                          params[10], params[11]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 13 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 14
    case 13:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6], params[7],
                                          params[8], params[9],
                                          params[10], params[11],
                                          params[12]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 14 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 15
    case 14:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6], params[7],
                                          params[8], params[9],
                                          params[10], params[11],
                                          params[12], params[13]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 15 */
#if CONFIG_SHELL_PARAM_MAX_NUM >= 16
    case 15:
        return command->data.cmd.function(params[0], params[1],
                                          params[2], params[3],
                                          params[4], params[5],
                                          params[6], params[7],
                                          params[8], params[9],
                                          params[10], params[11],
                                          params[12], params[13],
                                          params[14]);
        // break;
#endif /** QE_SHELL_PARAMETER_MAX_NUMBER >= 16 */
    default:
        return -1;
        // break;
    }
}

static qe_base shell_show_var(qe_shell *shell, struct qe_shell_command *command)
{
    int i;
    char buffer[12] = "00000000000";
    qe_base value = shell_get_var_value(shell, command);
    
    write_string(shell, "\r\n");
    write_string(shell, command->data.var.name);
    write_string(shell, " = ");

    switch (command->attr.attrs.type)
    {
    case QE_SHELL_TYPE_VAR_STRING:
        write_string(shell, "\"");
        write_string(shell, (char *)((qe_base)value));
        write_string(shell, "\"");
        break;
    // case QE_SHELL_TYPE_VAR_INT:
    // case QE_SHELL_TYPE_VAR_SHORT:
    // case QE_SHELL_TYPE_VAR_CHAR:
    // case QE_SHELL_TYPE_VAR_POINT:
    default:
        write_string(shell, &buffer[11 - qe_shello_dec(value, buffer)]);
        write_string(shell, ", 0x");
        for (i = 0; i < 11; i++)
        {
            buffer[i] = '0';
        }
        qe_shello_hex(value, buffer);
        write_string(shell, buffer);
        break;
    }

    write_string(shell, "\r\n");
    return value;
}

unsigned int shell_run_command(qe_shell *sh, qe_shell_command *cmd)
{
    int ret = 0;
    sh->status.active = 1;

    if (cmd->attr.attrs.type == QE_SHELL_TYPE_CMD_MAIN) {
        shell_remove_param_quotes(sh);
        ret = cmd->data.cmd.function(sh->parser.nr_params,
            sh->parser.param);
        if (cmd->attr.attrs.enable_return) {
            shell_write_return_value(sh, ret);
        }
    } else if (cmd->attr.attrs.type == QE_SHELL_TYPE_CMD_FUNC) {
        ret = shell_ext_run(sh, cmd, sh->parser.nr_params, sh->parser.param);
        if (cmd->attr.attrs.enable_return) {
            shell_write_return_value(sh, ret);
        }
    } else if (cmd->attr.attrs.type >= QE_SHELL_TYPE_VAR_INT
        && cmd->attr.attrs.type <= QE_SHELL_TYPE_VAR_NODE) {
        shell_show_var(sh, cmd);
    }
    sh->status.active = 0;

    return ret;
}

static void shell_check_password(qe_shell *sh)
{
    char *buffer = (char *)sh->parser.buf->p;
    if (qe_strcmp(buffer, sh->info.user->data.usr.password) == 0)
    {
        sh->status.checked = 1;
#if defined(CONFIG_SHELL_SHOW_INFO)
        write_string(sh, qe_shellext[SHELL_TXT_INFO]);
#endif
#if defined(CONFIG_SHELL_SHOW_USR_INFO)
        if (sh->usr_info)
            write_string(sh, sh->usr_info);
#endif
    }
    else
    {
        write_string(sh, qe_shellext[SHELL_TXT_PASSWORD_ERROR]);
    }
    sh->parser.length = 0;
    sh->parser.cursor = 0;
}

void shell_exec(qe_shell *sh)
{
    char *buffer = (char *)sh->parser.buf->p;
    struct qe_shell_command *cmd;

    if (sh->parser.length == 0) {
        return;
    }

    buffer[sh->parser.length] = 0;

    if (sh->status.checked)
    {
    #if CONFIG_SHELL_HISTORY_MAX_NUM > 0
        shell_history_add(sh);
    #endif /** CONFIG_SHELL_HISTORY_MAX_NUM > 0 */
        shell_parser_param(sh);
        sh->parser.length = sh->parser.cursor = 0;
        if (sh->parser.nr_params == 0)
        {
            return;
        }
        cmd = shell_seek_command(sh, sh->parser.param[0],
            sh->cmdtab.base, 0);
        if (cmd != QE_NULL) {
            write_string(sh, "\r\n");
            shell_run_command(sh, cmd);
        } else {
            write_string(sh, qe_shellext[SHELL_TXT_CMD_NOT_FOUND]);
        }
    } else {
        shell_check_password(sh);
    }
}

void qe_shell_key_up(qe_shell *sh)
{
    shell_history(sh, 1);
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_KEY(QE_SHELL_KEY_CODE_UP, qe_shell_key_up, up);
#endif

void qe_shell_key_down(qe_shell *sh)
{
    shell_history(sh, -1);
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_KEY(QE_SHELL_KEY_CODE_DOWN, qe_shell_key_up, down);
#endif

void qe_shell_key_right(qe_shell *sh)
{
    char *buffer = (char *)sh->parser.buf->p;
    if (sh->parser.cursor < sh->parser.length)
    {
        write_byte(sh, buffer[sh->parser.cursor++]);
    }
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_KEY(QE_SHELL_KEY_CODE_RIGHT, qe_shell_key_right, right);
#endif

void qe_shell_key_left(qe_shell *sh)
{
    if (sh->parser.cursor > 0)
    {
        write_byte(sh, '\b');
        sh->parser.cursor--;
    }
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_KEY(QE_SHELL_KEY_CODE_LEFT, qe_shell_key_left, left);
#endif

static void write_prompt(qe_shell *sh, unsigned char newline)
{
    if (sh->status.checked) {
        if (newline) {
            write_string(sh, "\r\n");
        }
        write_string(sh, sh->info.user->data.usr.name);
        write_string(sh, ":");
        write_string(sh, sh->info.path ? sh->info.path : "/");
        write_string(sh, "$ ");
    } else {
        write_string(sh, qe_shellext[SHELL_TXT_PASSWORD_HINT]);
    }
}

void qe_shell_key_enter(qe_shell *sh)
{
    shell_exec(sh);
    write_prompt(sh, 1);
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_KEY(QE_SHELL_KEY_CODE_ENTER, qe_shell_key_enter, enter);
#endif

void shell_delete_command_line(qe_shell *sh, unsigned char length)
{
    while (length--)
    {
        write_string(sh, "\b \b");
    }
}

void shell_delete_byte(qe_shell *shell, signed char direction)
{
    char offset = (direction == -1) ? 1 : 0;

    if ((shell->parser.cursor == 0 && direction == 1)
        || (shell->parser.cursor == shell->parser.length && direction == -1))
    {
        return;
    }
    if (shell->parser.cursor == shell->parser.length && direction == 1)
    {
        shell->parser.cursor--;
        shell->parser.length--;
        shell->parser.buf->p[shell->parser.length] = 0;
        shell_delete_command_line(shell, 1);
    }
    else
    {
        for (short i = offset; i < shell->parser.length - shell->parser.cursor; i++)
        {
            shell->parser.buf->p[shell->parser.cursor + i - 1] = 
                shell->parser.buf->p[shell->parser.cursor + i];
        }
        shell->parser.length--;
        if (!offset)
        {
            shell->parser.cursor--;
            write_byte(shell, '\b');
        }
        shell->parser.buf->p[shell->parser.length] = 0;
        for (short i = shell->parser.cursor; i < shell->parser.length; i++)
        {
            write_byte(shell, shell->parser.buf->p[i]);
        }
        write_byte(shell, ' ');
        for (short i = shell->parser.length - shell->parser.cursor + 1; i > 0; i--)
        {
            write_byte(shell, '\b');
        }
    }
}

void qe_shell_key_backspace(qe_shell *sh)
{
    shell_delete_byte(sh, 1);
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_KEY(QE_SHELL_KEY_CODE_BACKSPACE, 
    qe_shell_key_backspace, backspace);
QE_SHELL_EXPORT_KEY(0x7F000000, 
    qe_shell_key_backspace, backspace);
#endif

static void shell_write_command_help(qe_shell *sh, char *name)
{
    struct qe_shell_command *cmd;
    
    cmd = shell_seek_command(sh, name, sh->cmdtab.base, 0);
    if (cmd) {
        write_string(sh, qe_shellext[SHELL_TXT_HELP_HEADER]);
        write_string(sh, shell_get_command_name(cmd));
        write_string(sh, "\r\n");
        write_string(sh, shell_get_command_desc(cmd));
        write_string(sh, "\r\n");
    } else {
        write_string(sh, qe_shellext[SHELL_TXT_CMD_NOT_FOUND]);
    }
}

void shell_list_item(qe_shell *sh, qe_shell_command *cmd)
{
    short spaceLength;

    spaceLength = 22 - write_string(sh, shell_get_command_name(cmd));
    spaceLength = (spaceLength > 0) ? spaceLength : 4;
    do {
        write_string(sh, " ");
    } while (--spaceLength);
    if (cmd->attr.attrs.type <= QE_SHELL_TYPE_CMD_FUNC)
    {
        write_string(sh, qe_shellext[SHELL_TXT_TYPE_CMD]);
    }
    else if (cmd->attr.attrs.type <= QE_SHELL_TYPE_VAR_NODE)
    {
        write_string(sh, qe_shellext[SHELL_TXT_TYPE_VAR]);
    }
    else if (cmd->attr.attrs.type <= QE_SHELL_TYPE_USR)
    {
        write_string(sh, qe_shellext[SHELL_TXT_TYPE_USER]);
    }
    else if (cmd->attr.attrs.type <= QE_SHELL_TYPE_KEY)
    {
        write_string(sh, qe_shellext[SHELL_TXT_TYPE_KEY]);
    }
    else
    {
        write_string(sh, qe_shellext[SHELL_TXT_TYPE_NONE]);
    }
#if QE_SHELL_HELP_SHOW_PERMISSION == 1
    write_string(shell, "  ");
    for (signed char i = 7; i >= 0; i--)
    {
        write_string(shell, item->attr.attrs.permission & (1 << i) ? 'x' : '-');
    }
#endif
    write_string(sh, "  ");
    shell_write_command_desc(sh, shell_get_command_desc(cmd));
    write_string(sh, "\r\n");
}

void shell_list_command(qe_shell *sh)
{
    int i;
    qe_shell_command *base = (qe_shell_command *)sh->cmdtab.base;
    write_string(sh, qe_shellext[SHELL_TXT_CMD_LIST]);
    for (i=0; i<sh->cmdtab.count; i++) {
        if (base[i].attr.attrs.type <= QE_SHELL_TYPE_CMD_FUNC &&
            base[i].attr.attrs.type > QE_SHELL_TYPE_CMD_NONE)
        {
            shell_list_item(sh, &base[i]);
        }
    }
}

void shell_list_var(qe_shell *sh)
{
    int i;
    qe_shell_command *base = (qe_shell_command *)sh->cmdtab.base;
    write_string(sh, qe_shellext[SHELL_TXT_VAR_LIST]);
    for (i=0; i<sh->cmdtab.count; i++) {
        if (base[i].attr.attrs.type > QE_SHELL_TYPE_CMD_FUNC
            && base[i].attr.attrs.type <= QE_SHELL_TYPE_VAR_NODE)
        {
            shell_list_item(sh, &base[i]);
        }
    }
}

void shell_list_all(qe_shell *shell)
{
#if defined(CONFIG_SHELL_HELP_LIST_USER)
    shell_list_user(shell);
#endif
    shell_list_command(shell);
#if defined(CONFIG_SHELL_HELP_LIST_VAR)
    shell_list_var(shell);
#endif
#if defined(CONFIG_SHELL_HELP_LIST_KEY)
    shell_list_key(shell);
#endif
}

void qe_shell_cmd_help(int argc, char *argv[])
{
    if (argc == 1) {
        shell_list_all(shctx);
    } else {
        shell_write_command_help(shctx, argv[1]);
    }
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_CMD_EXEC(help, qe_shell_cmd_help, help information);
#endif

int shell_set_var_value(qe_shell *sh, qe_shell_command *cmd, int value)
{
    if (cmd->attr.attrs.read_only) {
        write_string(sh, qe_shellext[SHELL_TXT_VAR_READ_ONLY_CANNOT_MODIFY]);
    } else {
        switch (cmd->attr.attrs.type)
        {
        case QE_SHELL_TYPE_VAR_INT:
            *((int *)(cmd->data.var.value)) = value;
            break;
        case QE_SHELL_TYPE_VAR_SHORT:
            *((short *)(cmd->data.var.value)) = value;
            break;
        case QE_SHELL_TYPE_VAR_CHAR:
            *((char *)(cmd->data.var.value)) = value;
            break;
        case QE_SHELL_TYPE_VAR_STRING:
            shell_string_copy(((char *)(cmd->data.var.value)), (char *)((qe_base)value));
            break;
        case QE_SHELL_TYPE_VAR_POINT:
            write_string(sh, qe_shellext[SHELL_TXT_POINT_CANNOT_MODIFY]);
            break;
        case QE_SHELL_TYPE_VAR_NODE:
            if (((qe_shell_node_attr *)cmd->data.var.value)->set)
            {
                if (((qe_shell_node_attr *)cmd->data.var.value)->var)
                {
                    ((qe_shell_node_attr *)cmd->data.var.value)
                        ->set(((qe_shell_node_attr *)cmd->data.var.value)->var, value);
                }
                else
                {
                    ((qe_shell_node_attr *)cmd->data.var.value)->set(value);
                }
            }
            break;
        default:
            break;
        }
    }
    return shell_show_var(sh, cmd);
}

int qe_shell_cmd_setvar(char *name, int value)
{
    qe_shell *sh = shctx;
    qe_shell_command *cmd = shell_seek_command(sh, name, sh->cmdtab.base, 0);
    if (!cmd) {
        write_string(sh, qe_shellext[SHELL_TXT_VAR_NOT_FOUND]);
        return 0;
    }
    if (cmd->attr.attrs.type < QE_SHELL_TYPE_VAR_INT
        || cmd->attr.attrs.type > QE_SHELL_TYPE_VAR_NODE)
    {
        write_string(sh, name);
        write_string(sh, qe_shellext[SHELL_TXT_NOT_VAR]);
        return 0;
    }
    return shell_set_var_value(sh, cmd, value);
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_CMD_FUNC(2, setvar, qe_shell_cmd_setvar, set variable);
#endif

int qe_shell_cmd_getvar(char *name)
{
    qe_shell *sh = shctx;
    qe_shell_command *cmd = shell_seek_command(sh, name, sh->cmdtab.base, 0);
    if (!cmd) {
        write_string(sh, qe_shellext[SHELL_TXT_VAR_NOT_FOUND]);
        return 0;
    }
    if (cmd->attr.attrs.type < QE_SHELL_TYPE_VAR_INT
        || cmd->attr.attrs.type > QE_SHELL_TYPE_VAR_NODE)
    {
        write_string(sh, name);
        write_string(sh, qe_shellext[SHELL_TXT_NOT_VAR]);
        return 0;
    }
    shell_show_var(sh, cmd);
}
#if defined(CONFIG_SHELL_USING_EXPORT)
QE_SHELL_EXPORT_CMD_FUNC(1, getvar, qe_shell_cmd_getvar, get variable);
#endif

void shell_insert_byte(qe_shell *sh, char data)
{
    int i;
    char *buffer = (char *)sh->parser.buf->p;
    /* 判断输入数据是否过长 */
    if (sh->parser.length >= sh->parser.bufsz - 1)
    {
        write_string(sh, qe_shellext[SHELL_TXT_CMD_TOO_LONG]);
        write_prompt(sh, 1);
        write_string(sh, buffer);
        return;
    }

    /* 插入数据 */
    if (sh->parser.cursor == sh->parser.length)
    {
        buffer[sh->parser.length++] = data;
        buffer[sh->parser.length] = 0;
        sh->parser.cursor++;
        if (sh->status.checked)
            write_byte(sh, data);
        else
            write_string(sh, "*");
    }
    else if (sh->parser.cursor < sh->parser.length)
    {
        for (i = sh->parser.length - sh->parser.cursor; i > 0; i--)
        {
            buffer[sh->parser.cursor + i] = 
                buffer[sh->parser.cursor + i - 1];
        }
        buffer[sh->parser.cursor++] = data;
        buffer[++sh->parser.length] = 0;
        for (i = sh->parser.cursor - 1; i < sh->parser.length; i++)
        {
            write_byte(sh, buffer[i]);
        }
        for (i = sh->parser.length - sh->parser.cursor; i > 0; i--)
        {
            write_byte(sh, '\b');
        }
    }
}

void shell_normal_input(qe_shell *sh, char data)
{
    sh->status.tab = 0;
    shell_insert_byte(sh, data);
}

void qe_shell_handler(qe_shell *sh, char data)
{
    int i;
    char key_offset = 24;
    int key_filter  = 0x00000000;

    if ((sh->parser.key & 0x0000FF00) != 0x00000000)
    {
        key_offset = 0;
        key_filter = 0xFFFFFF00;
    }
    else if ((sh->parser.key & 0x00FF0000) != 0x00000000)
    {
        key_offset = 8;
        key_filter = 0xFFFF0000;
    }
    else if ((sh->parser.key & 0xFF000000) != 0x00000000)
    {
        key_offset = 16;
        key_filter = 0xFF000000;
    }

    qe_shell_command *base = (qe_shell_command *)sh->cmdtab.base;

    for (i=0; i < sh->cmdtab.count; i++) {
        
        if (base[i].attr.attrs.type == QE_SHELL_TYPE_KEY) {
            if ((base[i].data.key.value & key_filter) == sh->parser.key
                && (base[i].data.key.value & (0xFF << key_offset))
                    == (data << key_offset)) {
                sh->parser.key |= data << key_offset;
                data = 0x00;
                if (key_offset == 0 ||
                    (base[i].data.key.value & (0xFF << (key_offset-8))) 
                    == 0x00000000) {
                    if (base[i].data.key.function) {
                        base[i].data.key.function(sh);
                    }
                
                    sh->parser.key = 0x00000000;
                    break;
                }
            }
        }
    }

    if (data != 0x00) {
        sh->parser.key = 0x00000000;
        shell_normal_input(sh, data);
    }
}

void qe_shell_task(qe_shell *sh)
{
    char data;

    while(1) {
        if (sh->read && sh->read(sh, &data, 1) == 1) {
            qe_shell_handler(sh, data);
        }
    }
}

static void shell_set_user(qe_shell *sh, const qe_shell_command *user)
{
    sh->info.user = user;
    sh->status.checked = 
        ((user->data.usr.password && qe_strlen(user->data.usr.password) != 0)
            && (sh->parser.nr_params < 2
                || qe_strcmp(user->data.usr.password, sh->parser.param[1]) != 0))
         ? 0 : 1;
#if QE_SHELL_CLS_WHEN_LOGIN == 1
    write_string(sh, qe_shellext[SHELL_TXT_CLEAR_CONSOLE]);
#endif
#if defined(CONFIG_SHELL_SHOW_INFO)
    if (sh->status.checked)
    {
        write_string(sh, qe_shellext[SHELL_TXT_INFO]);
    }
#endif
}

#if CONFIG_SHELL_SUPPORT_END_LINE
void qe_shell_write_end_line(qe_shell *sh, char *buf, int len)
{
    int i;
    if (!sh->status.active) {
        write_string(sh, qe_shellext[SHELL_TXT_CLEAR_LINE]);
    }
    
    sh->write(sh, buf, len);

    if (!sh->status.active) {
        write_prompt(sh, 0);
        if (sh->parser.length > 0) {
            write_string(sh, sh->parser.buf->p);
            for (i=0; i<sh->parser.length - sh->parser.cursor; i++) {
                write_byte(sh, '\b');
            }
        }
    }
}
#endif

#if defined(CONFIG_SHELL_SHOW_USR_INFO)
qe_ret qe_shell_set_usr_info(qe_shell *sh, const char *usr_info)
{
    if (!sh->status.initialized || !shctx) {
        qe_error("shell uninitialized");
        return qe_err_param;
    }

    sh->usr_info = usr_info;

    return qe_ok;
}
#endif

qe_ret qe_shell_init(
    qe_shell *sh, 
    qe_size bufsz,
#ifndef CONFIG_SHELL_USING_EXPORT
    void *tab, 
    int tabsize,
#endif
    qe_const_str username,
    qe_shell_io read, 
    qe_shell_io write)
{
    int i;

    if (sh->status.initialized || shctx) {
        qe_error("shell initialized");
        return qe_err_exist;
    }

    sh->read             = read;
    sh->write            = write;

    sh->status.checked   = 0;

    sh->parser.nr_params = 0;
    sh->parser.cursor    = 0;
    sh->parser.buf       = qe_gbuf_new(bufsz);
    sh->parser.bufsz     = bufsz / (CONFIG_SHELL_HISTORY_MAX_NUM + 1);

#if CONFIG_SHELL_HISTORY_MAX_NUM > 0
    sh->history.offset = 0;
    sh->history.number = 0;
    sh->history.record = 0;
    for (i=0; i<CONFIG_SHELL_HISTORY_MAX_NUM; i++) {
        sh->history.item[i] = (char *)(sh->parser.buf->p) + 
            sh->parser.bufsz * (i + 1);
    }
#endif /** CONFIG_SHELL_HISTORY_MAX_NUM > 0 */

    sh->cmdtab.base  = QE_NULL;
    sh->cmdtab.count = 0;

#if defined(CONFIG_SHELL_USING_EXPORT)
    #if defined(__CC_ARM) || (defined(__ARMCC_VERSION) && __ARMCC_VERSION >= 6000000)
        sh->cmdtab.base = (qe_shell_command *)&qe_shellcommand$$base;
        sh->cmdtab.count = ((qe_size)(&qe_shellcommand$$limit)
                                - (qe_size)(&qe_shellcommand$$base))
                                / sizeof(qe_shell_command);
    #elif defined(__ICCARM__) || defined(__ICCRX__)
        sh->cmdtab.base = (qe_shell_command *)(__section_begin("qe_shellcommand"));
        sh->cmdtab.count = ((qe_size)(__section_end("qe_shellcommand"))
                                - (qe_size)(__section_begin("qe_shellcommand")))
                                / sizeof(qe_shell_command);
    #elif defined(__GNUC__)
        sh->cmdtab.base = (qe_shell_command *)(&qe_shellcmd_start);
        sh->cmdtab.count = ((qe_size)(&qe_shellcmd_end)
                                - (qe_size)(&qe_shellcmd_start))
                                / sizeof(qe_shell_command);
    #else
        #error not supported compiler, please use command table mode
    #endif

    if (!sh->cmdtab.base || sh->cmdtab.count < 6) {
        qe_error("symbol table no command table");
        qe_free(sh->parser.buf);
        return qe_err_param;
    }

#else
    if (!tab || !tabsize) {
        qe_error("no command table");
        qe_free(sh->parser.buf);
        return qe_err_param;
    }
    sh->cmdtab.base = (qe_shell_command *)tab;
    sh->cmdtab.count = tabsize;
#endif

    shell_set_user(sh, shell_seek_command(sh, username, sh->cmdtab.base, 0));
    write_prompt(sh, 1);

#if defined(CONFIG_SHELL_SHOW_USR_INFO)
    sh->usr_info = QE_NULL;
#endif

    sh->status.initialized = 1;
    shctx = sh;
    return qe_ok;
}

void qe_shellcmd_force_include(void)
{
#if defined(CONFIG_CMD_LOG)
    QE_SHELLCMD_FORCE_IMPORT(log);
#endif

#if defined(CONFIG_CMD_GPIO)
    QE_SHELLCMD_FORCE_IMPORT(gpio);
#endif

#if defined(CONFIG_CMD_MEM)
    QE_SHELLCMD_FORCE_IMPORT(free);
#endif

#if defined(CONFIG_CMD_I2C)
    QE_SHELLCMD_FORCE_IMPORT(i2c);
#endif
}

void qe_shell_set_priv(qe_shell *sh, qe_ptr priv)
{
    if (!sh) {
        qe_error("shell null");
        return;
    }

    sh->priv = priv;
}

void *qe_shell_get_priv(qe_shell *sh)
{
    if (!sh)
        return QE_NULL;
    return sh->priv;
}